(function ($) {
    var CLASS_PULL_TOP_TIPS = $.className('pull-top-tips');

    $.PullToRefresh = $.PullToRefresh.extend({
        init: function (element, options) {
            this._super(element, options);
            this.options = $.extend(true, {
                down: {
                    tips: {
                        colors: ['008000', 'd8ad44', 'd00324', 'dc00b8', '017efc'],
                        size: 200, //width=height=size;x=y=size/2;radius=size/4
                        lineWidth: 15,
                        duration: 1000,
                        tail_duration: 1000 * 2.5
                    }
                }
            }, this.options);
            this.options.down.tips.color = this.options.down.tips.colors[0];
            this.options.down.tips.colors = this.options.down.tips.colors.map(function (color) {
                return {
                    r: parseInt(color.substring(0, 2), 16),
                    g: parseInt(color.substring(2, 4), 16),
                    b: parseInt(color.substring(4, 6), 16)
                };
            });
        },
        initPullDownTips: function () {
            var self = this;
            if ($.isFunction(self.options.down.callback)) {
                self.pullDownTips = (function () {
                    var element = document.querySelector('.' + CLASS_PULL_TOP_TIPS);
                    if (element) {
                        element.parentNode.removeChild(element);
                    }
                    if (!element) {
                        element = document.createElement('div');
                        element.classList.add(CLASS_PULL_TOP_TIPS);
                        element.innerHTML = '<div class="mui-pull-top-wrapper"><div class="mui-pull-top-canvas"><canvas id="pullDownTips" width="' + self.options.down.tips.size + '" height="' + self.options.down.tips.size + '"></canvas></div></div>';
                        element.addEventListener('webkitTransitionEnd', self);
                        document.body.appendChild(element);
                    }
                    self.pullDownCanvas = document.getElementById("pullDownTips");
                    self.pullDownCanvasCtx = self.pullDownCanvas.getContext('2d');
                    self.canvasUtils.init(self.pullDownCanvas, self.options.down.tips);
                    return element;
                }());
            }
        },
        removePullDownTips: function () {
            this._super();
            this.canvasUtils.stopSpin();
        },
        pulling: function (deltaY) {
            var ratio = Math.min(deltaY / (this.options.down.height * 1.5), 1);
            var ratioPI = Math.min(1, ratio * 2);
            this.pullDownTips.style.webkitTransform = 'translate3d(0,' + (deltaY < 0 ? 0 : deltaY) + 'px,0)';
            this.pullDownCanvas.style.opacity = ratioPI;
            this.pullDownCanvas.style.webkitTransform = 'rotate(' + 300 * ratio + 'deg)';
            var canvas = this.pullDownCanvas;
            var ctx = this.pullDownCanvasCtx;
            var size = this.options.down.tips.size;
            ctx.lineWidth = this.options.down.tips.lineWidth;
            ctx.fillStyle = '#' + this.options.down.tips.color;
            ctx.strokeStyle = '#' + this.options.down.tips.color;
            ctx.stroke();
            ctx.clearRect(0, 0, size, size);
            //fixed android 4.1.x
            canvas.style.display = 'none'; // Detach from DOM
            canvas.offsetHeight; // Force the detach
            canvas.style.display = 'inherit'; // Reattach to DOM
            this.canvasUtils.drawArcedArrow(ctx, size / 2 + 0.5, size / 2, size / 4, 0 * Math.PI, 5 / 3 * Math.PI * ratioPI, false, 1, 2, 0.7853981633974483, 25, this.options.down.tips.lineWidth, this.options.down.tips.lineWidth);
        },

        beforeChangeOffset: function (deltaY) {
        },
        afterChangeOffset: function (deltaY) {
        },
        dragEndAfterChangeOffset: function (isNeedRefresh) {
            if (isNeedRefresh) {
                this.canvasUtils.startSpin();
                this.pullDownLoading();
            } else {
                this.canvasUtils.stopSpin();
                this.endPullDownToRefresh();
            }
        },
        canvasUtils: (function () {
            var canvasObj = null,
                ctx = null,
                size = 200,
                lineWidth = 15,
                tick = 0,
                startTime = 0,
                frameTime = 0,
                timeLast = 0,
                oldStep = 0,
                acc = 0,
                head = 0,
                tail = 180,
                rad = Math.PI / 180,
                duration = 1000,
                tail_duration = 1000 * 2.5,
                colors = ['35ad0e', 'd8ad44', 'd00324', 'dc00b8', '017efc'],
                rAF = null;

            function easeLinear(currentIteration, startValue, changeInValue, totalIterations) {
                return changeInValue * currentIteration / totalIterations + startValue;
            }

            function easeInOutQuad(currentIteration, startValue, changeInValue, totalIterations) {
                if ((currentIteration /= totalIterations / 2) < 1) {
                    return changeInValue / 2 * currentIteration * currentIteration + startValue;
                }
                return -changeInValue / 2 * ((--currentIteration) * (currentIteration - 2) - 1) + startValue;
            }

            function minmax(value, v0, v1) {
                var min = Math.min(v0, v1);
                var max = Math.max(v0, v1);
                if (value < min)
                    return min;
                if (value > max)
                    return min;
                return value;
            }

            var drawHead = function (ctx, x0, y0, x1, y1, x2, y2, style) {
                'use strict';
                if (typeof(x0) == 'string') x0 = parseInt(x0);
                if (typeof(y0) == 'string') y0 = parseInt(y0);
                if (typeof(x1) == 'string') x1 = parseInt(x1);
                if (typeof(y1) == 'string') y1 = parseInt(y1);
                if (typeof(x2) == 'string') x2 = parseInt(x2);
                if (typeof(y2) == 'string') y2 = parseInt(y2);
                var radius = 3;
                var twoPI = 2 * Math.PI;
                ctx.save();
                ctx.beginPath();
                ctx.moveTo(x0, y0);
                ctx.lineTo(x1, y1);
                ctx.lineTo(x2, y2);
                switch (style) {
                    case 0:
                        var backdist = Math.sqrt(((x2 - x0) * (x2 - x0)) + ((y2 - y0) * (y2 - y0)));
                        ctx.arcTo(x1, y1, x0, y0, .55 * backdist);
                        ctx.fill();
                        break;
                    case 1:
                        ctx.beginPath();
                        ctx.moveTo(x0, y0);
                        ctx.lineTo(x1, y1);
                        ctx.lineTo(x2, y2);
                        ctx.lineTo(x0, y0);
                        ctx.fill();
                        break;
                    case 2:
                        ctx.stroke();
                        break;
                    case 3:
                        var cpx = (x0 + x1 + x2) / 3;
                        var cpy = (y0 + y1 + y2) / 3;
                        ctx.quadraticCurveTo(cpx, cpy, x0, y0);
                        ctx.fill();
                        break;
                    case 4:
                        var cp1x, cp1y, cp2x, cp2y, backdist;
                        var shiftamt = 5;
                        if (x2 == x0) {
                            backdist = y2 - y0;
                            cp1x = (x1 + x0) / 2;
                            cp2x = (x1 + x0) / 2;
                            cp1y = y1 + backdist / shiftamt;
                            cp2y = y1 - backdist / shiftamt;
                        } else {
                            backdist = Math.sqrt(((x2 - x0) * (x2 - x0)) + ((y2 - y0) * (y2 - y0)));
                            var xback = (x0 + x2) / 2;
                            var yback = (y0 + y2) / 2;
                            var xmid = (xback + x1) / 2;
                            var ymid = (yback + y1) / 2;
                            var m = (y2 - y0) / (x2 - x0);
                            var dx = (backdist / (2 * Math.sqrt(m * m + 1))) / shiftamt;
                            var dy = m * dx;
                            cp1x = xmid - dx;
                            cp1y = ymid - dy;
                            cp2x = xmid + dx;
                            cp2y = ymid + dy;
                        }
                        ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x0, y0);
                        ctx.fill();
                        break;
                }
                ctx.restore();
            };
            var drawArcedArrow = function (ctx, x, y, r, startangle, endangle, anticlockwise, style, which, angle, d, lineWidth, lineRatio) {
                'use strict';
                style = typeof(style) != 'undefined' ? style : 3;
                which = typeof(which) != 'undefined' ? which : 1;
                angle = typeof(angle) != 'undefined' ? angle : Math.PI / 8;
                lineWidth = lineWidth || 1;
                lineRatio = lineRatio || 10;
                d = typeof(d) != 'undefined' ? d : 10;
                ctx.save();
                ctx.lineWidth = lineWidth;
                ctx.beginPath();
                ctx.arc(x, y, r, startangle, endangle, anticlockwise);
                ctx.stroke();
                var sx, sy, lineangle, destx, desty;
                if (which & 1) {
                    sx = Math.cos(startangle) * r + x;
                    sy = Math.sin(startangle) * r + y;
                    lineangle = Math.atan2(x - sx, sy - y);
                    if (anticlockwise) {
                        destx = sx + 10 * Math.cos(lineangle);
                        desty = sy + 10 * Math.sin(lineangle);
                    } else {
                        destx = sx - 10 * Math.cos(lineangle);
                        desty = sy - 10 * Math.sin(lineangle);
                    }
                    drawArrow(ctx, sx, sy, destx, desty, style, 2, angle, d);
                }
                if (which & 2) {
                    sx = Math.cos(endangle) * r + x;
                    sy = Math.sin(endangle) * r + y;
                    lineangle = Math.atan2(x - sx, sy - y);
                    if (anticlockwise) {
                        destx = sx - 10 * Math.cos(lineangle);
                        desty = sy - 10 * Math.sin(lineangle);
                    } else {
                        destx = sx + 10 * Math.cos(lineangle);
                        desty = sy + 10 * Math.sin(lineangle);
                    }
                    drawArrow(ctx, sx - lineRatio * Math.sin(endangle), sy + lineRatio * Math.cos(endangle), destx - lineRatio * Math.sin(endangle), desty + lineRatio * Math.cos(endangle), style, 2, angle, d)
                }
                ctx.restore();
            }
            var drawArrow = function (ctx, x1, y1, x2, y2, style, which, angle, d) {
                'use strict';
                if (typeof(x1) == 'string') x1 = parseInt(x1);
                if (typeof(y1) == 'string') y1 = parseInt(y1);
                if (typeof(x2) == 'string') x2 = parseInt(x2);
                if (typeof(y2) == 'string') y2 = parseInt(y2);
                style = typeof(style) != 'undefined' ? style : 3;
                which = typeof(which) != 'undefined' ? which : 1;
                angle = typeof(angle) != 'undefined' ? angle : Math.PI / 8;
                d = typeof(d) != 'undefined' ? d : 10;
                var toDrawHead = typeof(style) != 'function' ? drawHead : style;
                var dist = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
                var ratio = (dist - d / 3) / dist;
                var tox, toy, fromx, fromy;
                if (which & 1) {
                    tox = Math.round(x1 + (x2 - x1) * ratio);
                    toy = Math.round(y1 + (y2 - y1) * ratio);
                } else {
                    tox = x2;
                    toy = y2;
                }
                if (which & 2) {
                    fromx = x1 + (x2 - x1) * (1 - ratio);
                    fromy = y1 + (y2 - y1) * (1 - ratio);
                } else {
                    fromx = x1;
                    fromy = y1;
                }
                ctx.beginPath();
                ctx.moveTo(fromx, fromy);
                ctx.lineTo(tox, toy);
                ctx.stroke();
                var lineangle = Math.atan2(y2 - y1, x2 - x1);
                var h = Math.abs(d / Math.cos(angle));
                if (which & 1) {
                    var angle1 = lineangle + Math.PI + angle;
                    var topx = x2 + Math.cos(angle1) * h;
                    var topy = y2 + Math.sin(angle1) * h;
                    var angle2 = lineangle + Math.PI - angle;
                    var botx = x2 + Math.cos(angle2) * h;
                    var boty = y2 + Math.sin(angle2) * h;
                    toDrawHead(ctx, topx, topy, x2, y2, botx, boty, style);
                }
                if (which & 2) {
                    var angle1 = lineangle + angle;
                    var topx = x1 + Math.cos(angle1) * h;
                    var topy = y1 + Math.sin(angle1) * h;
                    var angle2 = lineangle - angle;
                    var botx = x1 + Math.cos(angle2) * h;
                    var boty = y1 + Math.sin(angle2) * h;
                    toDrawHead(ctx, topx, topy, x1, y1, botx, boty, style);
                }
            };

            var spinColors = function (currentIteration, totalIterations) {
                var step = currentIteration % totalIterations;
                if (step < oldStep)
                    colors.push(colors.shift());
                var c0 = colors[0],
                    c1 = colors[1],
                    r = minmax(easeLinear(step, c0.r, c1.r - c0.r, totalIterations), c0.r, c1.r),
                    g = minmax(easeLinear(step, c0.g, c1.g - c0.g, totalIterations), c0.g, c1.g),
                    b = minmax(easeLinear(step, c0.b, c1.b - c0.b, totalIterations), c0.b, c1.b);

                oldStep = step;
                return "rgb(" + parseInt(r) + "," + parseInt(g) + "," + parseInt(b) + ")";
            }

            var spin = function (t) {
                var timeCurrent = t || (new Date).getTime();
                if (!startTime) {
                    startTime = timeCurrent;
                }
                tick = timeCurrent - startTime;
                acc = easeInOutQuad((tick + tail_duration / 2) % tail_duration, 0, duration, tail_duration);
                head = easeLinear((tick + acc) % duration, 0, 360, duration);
                tail = 20 + Math.abs(easeLinear((tick + tail_duration / 2) % tail_duration, -300, 600, tail_duration));

                ctx.lineWidth = lineWidth;
                ctx.lineCap = "round";

                ctx.strokeStyle = spinColors(tick, duration);
                ctx.clearRect(0, 0, size, size);
                //fixed android 4.1.x
                canvasObj.style.display = 'none'; // Detach from DOM
                canvasObj.offsetHeight; // Force the detach
                canvasObj.style.display = 'inherit'; // Reattach to DOM
                ctx.beginPath();
                ctx.arc(size / 2, size / 2, size / 4, parseInt(head - tail) % 360 * rad, parseInt(head) % 360 * rad, false);
                ctx.stroke();

                rAF = requestAnimationFrame(spin);
            };
            var startSpin = function () {
                startTime = 0;
                oldStep = 0;
                rAF = requestAnimationFrame(spin);
            };
            var stopSpin = function () {
                rAF && cancelAnimationFrame(rAF);
            }
            var init = function (canvas, options) {
                canvasObj = canvas;
                ctx = canvasObj.getContext('2d');
                var options = $.extend(true, {}, options);
                colors = options.colors;
                duration = options.duration;
                tail_duration = options.tail_duration;
                size = options.size;
                lineWidth = options.lineWidth;
            };
            return {
                init: init,
                drawArcedArrow: drawArcedArrow,
                startSpin: startSpin,
                stopSpin: stopSpin
            };
        })()
    });
})(mui);